/*
 * Copyright (c) 1999, 2013, Oracle and/or its affiliates. All rights reserved.
 * ORACLE PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 */

package javax.management;

import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
import java.io.StreamCorruptedException;
import java.util.Objects;

/**
 * <p>Provides general information for an MBean descriptor object.
 * The feature described can be an attribute, an operation, a
 * parameter, or a notification.  Instances of this class are
 * immutable.  Subclasses may be mutable but this is not
 * recommended.</p>
 *
 * @since 1.5
 */
public class MBeanFeatureInfo implements Serializable, DescriptorRead {


  /* Serial version */
  static final long serialVersionUID = 3952882688968447265L;

  /**
   * The name of the feature.  It is recommended that subclasses call
   * {@link #getName} rather than reading this field, and that they
   * not change it.
   *
   * @serial The name of the feature.
   */
  protected String name;

  /**
   * The human-readable description of the feature.  It is
   * recommended that subclasses call {@link #getDescription} rather
   * than reading this field, and that they not change it.
   *
   * @serial The human-readable description of the feature.
   */
  protected String description;

  /**
   * @serial The Descriptor for this MBeanFeatureInfo.  This field can be null, which is equivalent
   * to an empty Descriptor.
   */
  private transient Descriptor descriptor;


  /**
   * Constructs an <CODE>MBeanFeatureInfo</CODE> object.  This
   * constructor is equivalent to {@code MBeanFeatureInfo(name,
   * description, (Descriptor) null}.
   *
   * @param name The name of the feature.
   * @param description A human readable description of the feature.
   */
  public MBeanFeatureInfo(String name, String description) {
    this(name, description, null);
  }

  /**
   * Constructs an <CODE>MBeanFeatureInfo</CODE> object.
   *
   * @param name The name of the feature.
   * @param description A human readable description of the feature.
   * @param descriptor The descriptor for the feature.  This may be null which is equivalent to an
   * empty descriptor.
   * @since 1.6
   */
  public MBeanFeatureInfo(String name, String description,
      Descriptor descriptor) {
    this.name = name;
    this.description = description;
    this.descriptor = descriptor;
  }

  /**
   * Returns the name of the feature.
   *
   * @return the name of the feature.
   */
  public String getName() {
    return name;
  }

  /**
   * Returns the human-readable description of the feature.
   *
   * @return the human-readable description of the feature.
   */
  public String getDescription() {
    return description;
  }

  /**
   * Returns the descriptor for the feature.  Changing the returned value
   * will have no affect on the original descriptor.
   *
   * @return a descriptor that is either immutable or a copy of the original.
   * @since 1.6
   */
  public Descriptor getDescriptor() {
    return (Descriptor) ImmutableDescriptor.nonNullDescriptor(descriptor).clone();
  }

  /**
   * Compare this MBeanFeatureInfo to another.
   *
   * @param o the object to compare to.
   * @return true if and only if <code>o</code> is an MBeanFeatureInfo such that its {@link
   * #getName()}, {@link #getDescription()}, and {@link #getDescriptor()} values are equal (not
   * necessarily identical) to those of this MBeanFeatureInfo.
   */
  public boolean equals(Object o) {
    if (o == this) {
      return true;
    }
    if (!(o instanceof MBeanFeatureInfo)) {
      return false;
    }
    MBeanFeatureInfo p = (MBeanFeatureInfo) o;
    return (Objects.equals(p.getName(), getName()) &&
        Objects.equals(p.getDescription(), getDescription()) &&
        Objects.equals(p.getDescriptor(), getDescriptor()));
  }

  public int hashCode() {
    return getName().hashCode() ^ getDescription().hashCode() ^
        getDescriptor().hashCode();
  }

  /**
   * Serializes an {@link MBeanFeatureInfo} to an {@link ObjectOutputStream}.
   *
   * @serialData For compatibility reasons, an object of this class is serialized as follows. <p>
   * The method {@link ObjectOutputStream#defaultWriteObject defaultWriteObject()} is called first
   * to serialize the object except the field {@code descriptor} which is declared as transient. The
   * field {@code descriptor} is serialized as follows: <ul> <li>If {@code descriptor} is an
   * instance of the class {@link ImmutableDescriptor}, the method {@link ObjectOutputStream#write
   * write(int val)} is called to write a byte with the value {@code 1}, then the method {@link
   * ObjectOutputStream#writeObject writeObject(Object obj)} is called twice to serialize the field
   * names and the field values of the {@code descriptor}, respectively as a {@code String[]} and an
   * {@code Object[]};</li> <li>Otherwise, the method {@link ObjectOutputStream#write write(int
   * val)} is called to write a byte with the value {@code 0}, then the method {@link
   * ObjectOutputStream#writeObject writeObject(Object obj)} is called to serialize directly the
   * field {@code descriptor}. </ul>
   * @since 1.6
   */
  private void writeObject(ObjectOutputStream out) throws IOException {
    out.defaultWriteObject();

    if (descriptor != null &&
        descriptor.getClass() == ImmutableDescriptor.class) {

      out.write(1);

      final String[] names = descriptor.getFieldNames();

      out.writeObject(names);
      out.writeObject(descriptor.getFieldValues(names));
    } else {
      out.write(0);

      out.writeObject(descriptor);
    }
  }

  /**
   * Deserializes an {@link MBeanFeatureInfo} from an {@link ObjectInputStream}.
   *
   * @serialData For compatibility reasons, an object of this class is deserialized as follows. <p>
   * The method {@link ObjectInputStream#defaultReadObject defaultReadObject()} is called first to
   * deserialize the object except the field {@code descriptor}, which is not serialized in the
   * default way. Then the method {@link ObjectInputStream#read read()} is called to read a byte,
   * the field {@code descriptor} is deserialized according to the value of the byte value: <ul>
   * <li>1. The method {@link ObjectInputStream#readObject readObject()} is called twice to obtain
   * the field names (a {@code String[]}) and the field values (a {@code Object[]}) of the {@code
   * descriptor}. The two obtained values then are used to construct an {@link ImmutableDescriptor}
   * instance for the field {@code descriptor};</li> <li>0. The value for the field {@code
   * descriptor} is obtained directly by calling the method {@link ObjectInputStream#readObject
   * readObject()}. If the obtained value is null, the field {@code descriptor} is set to {@link
   * ImmutableDescriptor#EMPTY_DESCRIPTOR EMPTY_DESCRIPTOR};</li> <li>-1. This means that there is
   * no byte to read and that the object is from an earlier version of the JMX API. The field {@code
   * descriptor} is set to {@link ImmutableDescriptor#EMPTY_DESCRIPTOR EMPTY_DESCRIPTOR}</li>
   * <li>Any other value. A {@link StreamCorruptedException} is thrown.</li> </ul>
   * @since 1.6
   */
  private void readObject(ObjectInputStream in)
      throws IOException, ClassNotFoundException {

    in.defaultReadObject();

    switch (in.read()) {
      case 1:
        final String[] names = (String[]) in.readObject();

        final Object[] values = (Object[]) in.readObject();
        descriptor = (names.length == 0) ?
            ImmutableDescriptor.EMPTY_DESCRIPTOR :
            new ImmutableDescriptor(names, values);

        break;
      case 0:
        descriptor = (Descriptor) in.readObject();

        if (descriptor == null) {
          descriptor = ImmutableDescriptor.EMPTY_DESCRIPTOR;
        }

        break;
      case -1: // from an earlier version of the JMX API
        descriptor = ImmutableDescriptor.EMPTY_DESCRIPTOR;

        break;
      default:
        throw new StreamCorruptedException("Got unexpected byte.");
    }
  }
}
